/***********************************************************************
代码主要实现了对RS485端口写操作的开发示范，当需要对RS485非阻塞性开发的时候
可做参考。开发只需要修改三处即可：
1 自定义宏“MBX_CMD_IVS_RESUL”
2 自定义数据结构“IVSResultAndLPWLInfo”
3 修改消息发送函数“OSAMbxSenRPC”
***********************************************************************/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <iostream>

#include "osa_mbx.h"
#include "json/json.h"
#include "util.h"

#include "OpenDevSDK.h"
#include "OpenDevSDKDefines.h"
#include "VzLPRClientSDK_WhiteListDefine.h"

#include "opendev.h"
#include "shm_data.h"

using namespace std;

OSA_MbxHndl         mbx_handle;     //OSA 消息邮箱 handle
aliiot_socket_t     aliiot_socket;  // local socket for aliiot
share_data_t *pshm;

#define MBX_CMD_IVS_RESULE      0   //OSA 车牌推送消息回调

#define PLATELENGTH             8   //车牌长度

//车牌推送回调函数
static void OnGetIVSResult2(const IVS_RESULT_PARAM_0 *pIVSResult,
    const VZ_LPWL_INFO *pWListInfo,
    void* pUserData)
{
    //OSA 开辟内存空间（内容交由 OSA 控制）
    void* ivsResultAndLpwl = OSA_memAlloc(sizeof(IVSResultAndLPWLInfo));

    printf("pWListInfo ucWLResult is %d\n", pWListInfo->ucWLResult);

    memcpy(&((IVSResultAndLPWLInfo*)ivsResultAndLpwl)->pIVSResult, pIVSResult, sizeof(IVS_RESULT_PARAM_0));
    memcpy(&((IVSResultAndLPWLInfo*)ivsResultAndLpwl)->pWListInfo, pWListInfo, sizeof(VZ_LPWL_INFO));

    //往 OSA 消息邮箱 mbx_handle 发送类型为 MBX_CMD_IVS_RESULE 参数为 ivsResultAndLpwl 的消息（OSA_MBX_FREE_PRM 表明内存释放交由 OSA 统一管理）
    OSA_mbxSendMsg(&mbx_handle, NULL, MBX_CMD_IVS_RESULE, ivsResultAndLpwl, OSA_MBX_FREE_PRM);
}

/* IVS to json */
/* 将要发送到服务器的信息转换成json格式 */
static std::string IVSResultToJson(IVSResultAndLPWLInfo *pIVSResultAndLpwl)
{
    VZ_HW_INFO HWInfo;
    UTF8Conv oConv;
    Json::Value root;
    Json::FastWriter fastwriter;
    unsigned char license[TH_LP_LEN] = {0};
    struct tm tmp_stm, *pstm;
    time_t tmp_time;

    memset(&HWInfo, 0, sizeof(HWInfo));
    VzOSDK_GetHWInfo(&HWInfo);
    
    //root["mac"] = HWInfo.mac;
    char serialno[20] = {0};
    sprintf(serialno, "%08x%08x", HWInfo.serialno[0], HWInfo.serialno[1]);
    root["deviceid"] = serialno;
    //root["softwareVersion"] = HWInfo.softwareVersion;
    oConv.iIConvToUTF8(pIVSResultAndLpwl->pIVSResult.plateInfo.license,
            license, TH_LP_LEN);
    root["license"] = (char *)license;
#if 0
    root["color"] = pIVSResultAndLpwl->pIVSResult.plateInfo.color;
    root["nColor"] = pIVSResultAndLpwl->pIVSResult.plateInfo.nColor;
    root["nType"] = pIVSResultAndLpwl->pIVSResult.plateInfo.nType;
    root["nConfidence"] = pIVSResultAndLpwl->pIVSResult.plateInfo.nConfidence;
    root["nBright"] = pIVSResultAndLpwl->pIVSResult.plateInfo.nBright;
    root["nDirection"] = pIVSResultAndLpwl->pIVSResult.plateInfo.nDirection;
    Json::Value rcLocation;
    rcLocation["left"] = pIVSResultAndLpwl->pIVSResult.plateInfo.rcLocation.left;
    rcLocation["top"] = pIVSResultAndLpwl->pIVSResult.plateInfo.rcLocation.top;
    rcLocation["right"] = pIVSResultAndLpwl->pIVSResult.plateInfo.rcLocation.right;
    rcLocation["bottom"] = pIVSResultAndLpwl->pIVSResult.plateInfo.rcLocation.bottom;
    root["rcLocation"] = rcLocation;
    root["nTime"] = pIVSResultAndLpwl->pIVSResult.plateInfo.nTime;
    Json::Value tvPTS;
    tvPTS["uTVSec"] = pIVSResultAndLpwl->pIVSResult.plateInfo.tvPTS.uTVSec;
    tvPTS["uTVUSec"] = pIVSResultAndLpwl->pIVSResult.plateInfo.tvPTS.uTVUSec;
    root["tvPTS"] = tvPTS;
    root["uBitsTrigType"] = pIVSResultAndLpwl->pIVSResult.plateInfo.uBitsTrigType;
    root["nCarBright"] = pIVSResultAndLpwl->pIVSResult.plateInfo.nCarBright;
    root["nCarColor"] = pIVSResultAndLpwl->pIVSResult.plateInfo.nCarColor;
    root["uId"] = pIVSResultAndLpwl->pIVSResult.plateInfo.uId;
#endif
    tmp_stm.tm_year = pIVSResultAndLpwl->pIVSResult.plateInfo.struBDTime.bdt_year - 1900;
    tmp_stm.tm_mon = pIVSResultAndLpwl->pIVSResult.plateInfo.struBDTime.bdt_mon - 1;
    tmp_stm.tm_mday = pIVSResultAndLpwl->pIVSResult.plateInfo.struBDTime.bdt_mday;
    tmp_stm.tm_hour = pIVSResultAndLpwl->pIVSResult.plateInfo.struBDTime.bdt_hour;
    tmp_stm.tm_min = pIVSResultAndLpwl->pIVSResult.plateInfo.struBDTime.bdt_min;
    tmp_stm.tm_sec = pIVSResultAndLpwl->pIVSResult.plateInfo.struBDTime.bdt_sec;
    tmp_stm.tm_isdst = 0;
    tmp_time = mktime(&tmp_stm);
    tmp_time -= 8 * 3600; // - 8 hours

    pstm = gmtime(&tmp_time);
    root["time"] = (Json::UInt64)mktime(pstm);
    root["parkingno"] = pshm->opendev_data.parkingNo;
    root["type"] = atoi(pshm->opendev_data.deviceType);

    std::string result = fastwriter.write(root);
    std::cout << result <<std::endl;

    return result;
}

//消息发送，开发可自定义修改，以满足业务需求
// OSA 消息监听（所有 OSA 消息都发送至该函数（由专用的 OSA 线程处理所有消息的发送））
static void OSAMbxSendRPC()
{
    int createMbx = OSA_mbxCreate(&mbx_handle);
    aliiot_socket_t *psocket = &aliiot_socket;

retry:
    if(0 == createMbx)
    {
        while(1)
        {
            //mbx_handle 接收到的消息存储到 msg_handle 中
            OSA_MsgHndl *msg_handle;
            int wait = OSA_mbxWaitMsg(&mbx_handle,&msg_handle);
            if(OSA_SOK != wait)
                break;

            //获取 msg_handle 的命令类型
            int cmd = OSA_msgGetCmd(msg_handle);

            //判断该消息的类型
            if(MBX_CMD_IVS_RESULE == cmd){
                //获取 msg_handle 的命令参数
                IVSResultAndLPWLInfo *pIVSResultAndLpwl = (IVSResultAndLPWLInfo*)OSA_msgGetPrm(msg_handle);

                if (psocket->isready && pshm->opendev_data.isready)
                {
                    std::string result = IVSResultToJson(pIVSResultAndLpwl);

#if 1
                    write(psocket->srv_sockfd, (void *)result.c_str(), result.size() - 1);
#else
                    char tmp[3000] = "{\"struBDTime\":\"2016-09-18 09:16:13\",\"serialno\":\"5dfsdfsdf4\",\"license\":\"浙B78998\"}";
                    write(psocket->srv_sockfd, (void *)tmp, strlen(tmp));
#endif
                }
                else
                {
                    printf("socket or share data is not ready!\n");
                }
            }

            //OSA 统一释放内存（参数为 OSA_MBX_FREE_PRM 的消息）
            OSA_mbxAckOrFreeMsg(msg_handle, 0);
        }
    }

    perror("OSA_mbxCreate fail! wait 1 second to retry. INFO");
    sleep(1);
    goto retry;

    OSA_mbxDelete(&mbx_handle);
}

//处理推送触发结果的回调函数
static void OnIOInput(GPIO_IN eId, int bValue, void* pUserData)
{
    printf("OnIOInput(%d) = %d\n", eId, bValue);
}

/* process received message */
/* 处理从阿里平台经由aliiot发送过来的消息 */
static void opendev_msg_proc(const char *buf)
{
    static Json::Reader *reader = new Json::Reader(Json::Features::strictMode());
    Json::Value root;

    if (!buf) return;

    printf("received message: %s\n", buf);

    /* IO operate */
    if (reader->parse(buf, root, false))
    {
        if (!root["GPIO_OUT1"].isNull())
        {
            VzOSDK_asynSetIOOutputStateAuto(GPIO_OUT1, 500);
        }
    }
    else
    {
        printf("The message is not json format!\n");
    }
}

/* opendev <-> aliiot rpc pthread */
/* 创建本地socket，负责opendev和aliiot程序之间通信 */
static void *opendev_rpc_pthread(void *param)
{
    int ret;
    char buf[MSG_LEN_MAX];
    aliiot_socket_t *psocket = &aliiot_socket;
    fd_set rfds;
    struct timeval tv;
    int num;
    int readretry = 0;

retry:
    memset(psocket, 0, sizeof(psocket));

    psocket->srv_sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (psocket->srv_sockfd < 0)
    {
        perror("create socket fail!. INFO");
        goto fail;
    }

    psocket->srv_addr.sun_family = AF_UNIX;
    strcpy(psocket->srv_addr.sun_path, UNIX_DOMAIN);

    ret = connect(psocket->srv_sockfd, (struct sockaddr *)&psocket->srv_addr, sizeof(psocket->srv_addr));
    if (-1 == ret)
    {
        perror("socket connect fail!. INFO");
        goto fail;
    }

    psocket->isready = 1;
    while (1)
    {
        if (0 == psocket->isready)
        {
            goto fail;
        }

        memset(buf, 0, MSG_LEN_MAX);
        FD_ZERO(&rfds);
        FD_SET(psocket->srv_sockfd, &rfds);
        tv.tv_sec = 1;
        tv.tv_usec = 0;

        ret = select(psocket->srv_sockfd + 1, &rfds, NULL, NULL, &tv);
        switch (ret)
        {
        case -1:
            perror("select fail!. INFO");
            goto fail;
            break;
        case 0:
            /* select timeout */
            break;
        default:
            if (FD_ISSET(psocket->srv_sockfd, &rfds))
            {
                num = read(psocket->srv_sockfd, buf, sizeof(buf));
                printf("Message from aliiot, size: %d\n", num);
                
                if (num > 0)
                {
                    printf("Message from aliiot, size: %d. processing...\n", num);
                    opendev_msg_proc(buf);
                }
                else
                {
                    if (readretry++ >= 3)
                    {
                        psocket->isready = 0;
                        readretry = 0;
                    }
                }
            }
            break;
        }
    }

fail:
    perror("opendev_rpc_pthread fail! wait 5 seconds to retry. INFO");
    if (psocket->srv_sockfd > 0)
    {
        close(psocket->srv_sockfd);
    }
    sleep(5);
    goto retry;

    return NULL;
}

int main(int argc, char* argv[])
{
    pthread_t pthread_rpc_id = 0;
    pthread_attr_t attr_rpc;
    VZ_HW_INFO HWInfo;

	printf("opendev start...\n");

	while (VzOSDK_Init())
    {
        printf("VzOSDK_Init fail! wait 5 seconds to retry\n");
        sleep(5);
    }

    while ((pshm = shm_data_init()) == NULL)
    {
        printf("shm_data_init fail! wait 5 seconds to retry\n");
        sleep(5);
    }

    memset(&HWInfo, 0, sizeof(HWInfo));
    VzOSDK_GetHWInfo(&HWInfo);
    memcpy((unsigned char *)&pshm->serialno, (unsigned char *)&HWInfo.serialno, sizeof(HWInfo.serialno));
    pshm->magic = SHARE_DATA_MAGIC;
    pshm->isready = 1;

pthread_fail:
    pthread_attr_init(&attr_rpc);
    pthread_attr_setdetachstate(&attr_rpc, PTHREAD_CREATE_DETACHED);
    pthread_create(&pthread_rpc_id, &attr_rpc, opendev_rpc_pthread, NULL);
    if (pthread_rpc_id <= 0)
    {
        printf("create pthread fail! wait 10 seconds to retry\n");
        sleep(10);
        goto pthread_fail;
    }

	//设置识别结果的回调函数
	VzOSDK_SetIVSResultCallBackEx2(OnGetIVSResult2, NULL);

	//设置IO输入的回调函数
	VzOSDK_SetIOInCallBack(OnIOInput, NULL);

    //开始监听 OSA 消息
    OSAMbxSendRPC();

    pthread_cancel(pthread_rpc_id);
	VzOSDK_Release();
	printf("Release sdk!\n");

	return 0;
}

